// 进程池的实现文件 , .hpp 头源可以写在一起
#pragma once // 防止头文件被重复包含
#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
// 进程池由一个一个信道组成 , 信道有自己的管理的数据结构

// 一个进程池子进程的个数 , 也代表几个信道
#define SUBPROCESSNUM 5

class Channel
{
public:
    Channel(int wfd, pid_t subid)
        : _wfd(wfd), _subprocessid(subid)
    {
        // 一个写端就表示一个信道 , 一个 subid 表示一个子进程
        _name = "Channel : " + std::to_string(_wfd) + " <-> subid : " + std::to_string(_subprocessid);
    }
    ~Channel() {}

    // 发送任务码 , 即 : 往写端写入
    void Send(int code)
    {
        write(_wfd, &code, sizeof(code));
    }

    // 关闭所有写端
    void Close()
    {
        close(_wfd);
    }

    // 回收所有子进程
    void Wait()
    {
        waitpid(_subprocessid, nullptr, 0);
    }

    // Get函数让外部用私有成员的第二种方法
    std::string &GetName() { return _name; }
    int GetSubid() { return _subprocessid; }

private:
    int _wfd;                   // 写端
    int _subprocessid;          // 子进程的 id , 代表是哪一个子进程
    std::string _name;          // 用于打印名称 , 方便我们查看创建情况
    friend class ChannelManage; // 友元类 , 让 ChannelManage 可以访问我的私有
};
/// **********  以上代表一个信道 **********

class ChannelManage
{
public:
    ChannelManage()
        : _next(0)
    {
    }
    ~ChannelManage()
    {
    }

    // 信道的管理 , 即 : 父进程创建一个信道就插入到ChannelManage 的 vector 中
    // 所以 , 插入的是一个信道即 : Channel , 即 : Channel 的对象 , 所以会走 Channel 的构造函数
    void Insert(int wfd, int subid)
    {
        _channels.emplace_back(wfd, subid); // emplace_back 效率比 push 系列较高
    }

    // 选择一个信道 , 轮询方式选择
    Channel &Select()
    {
        auto &c = _channels[_next];
        _next++;
        _next %= _channels.size(); // 末尾了返回开头继续
        return c;
    }

    // 打印信道用于测试信道的创建
    void PrintChannels()
    {
        // 每次遍历取到的就是每一个类型 , 即 : 一个一个的信道
        for (auto &channel : _channels)
        {
            std::cout << channel.GetName() << std::endl; // 因为加了友元类 , 所以我可以访问 Channel 的私有
        }
    }

    // 关闭子进程继承下来的所有 w 端
    void CloseAll()
    {
        std::cout << "--------------" << std::endl;
        for (auto &channel : _channels)
        {
            channel.Close();
            std::cout << "[ " << channel.GetSubid() << " ] 正在关闭继承下来的写端 [" << channel._wfd << "]" << std::endl;
        }
        std::cout << "--------------" << std::endl;
    }

    void CloseAndWait()
    {
        // 关闭所有写端
        // 遍历的方式关闭
        // 关闭和回收之前 , 先要把子进程继承下来的wfd端给关闭
        for (auto &channel : _channels)
        {
             std::cout << "--------------" << std::endl;
            channel.Close();
            std::cout << "wfd [" << channel._wfd << "] is closed !" << std::endl;
            channel.Wait();
            std::cout << "subprocess [" << channel._subprocessid << "is recycle !" << std::endl;

            std::cout << "--------------" << std::endl;
        }
    }

private:
    std::vector<Channel> _channels; // 表示信道的管理列表 , 用 STL - vector 管理
    int _next;
};
/// **********  以上代表信道之间的管理 , 对各个信道的组织 **********

class ProcessPool
{

private:
    // 子进程完成工作 , 子进程怎么完成工作 ?? 要从管道文件中读取父进程下发下来的任务码告诉子进程完成哪个工作
    void Work(int rfd) // 读端
    {
        // 要从管道文件中读取任务码 , 子进程不断读
        while (true)
        {
            int code = 0;
            ssize_t ret = read(rfd, &code, sizeof(code)); // read 第三个参数是期望读多少字节
            if (ret > 0)
            {
                // 读取成功 , 但是我们设定父进程下发的任务码是 int 类型 , 4个字节 ,所以读到 4个字节再停止
                if (ret != sizeof(code))
                    continue;
                // 正式读取成功 , 根据任务码完成对应的任务
                std::cout << "subprocess receive taskcode : " << code << std::endl;
                // 收到任务码以后 , 就要根据对应任务码执行对应的任务
                _tasks.Excute(code);
                std::cout << "----------------" << std::endl;
            }
            else if (ret == 0)
            {
                // 读取管道文件到结尾
                std::cout << "subprocess quit !" << std::endl;
                break;
            }
            else
            {
                // 读取管道文件错误
                std::cout << " read error !" << std::endl;
                break;
            }
        }
    }

    // 创建进程池 , 即 : 创建信道结构 , 3步
    bool Create()
    {
        // 进程池有很多信道
        for (int i = 0; i < SUBPROCESSNUM; ++i)
        {
            //// ******  以下是创建一个信道的过程  ******

            // 1. 创建管道
            int pipefd[2] = {0};
            int p = pipe(pipefd);
            if (p < 0)
            {
                std::cerr << "pipe fail !" << std::endl;
                return false;
            }

            // 2. 创建子进程
            pid_t subid = fork();
            if (subid < 0)
            {
                std::cerr << "fork fail !" << std::endl;
                return false;
            }
            else if (subid == 0)
            {
                // 先关闭继承下来的所有写端 , 只关闭的是继承下来的 , 并非创建出来的!!
                _chm.CloseAll();
                // child
                // 3. 关闭各自无关端 , 子进程关闭写端
                close(pipefd[1]);

                //...子进程工作
                Work(pipefd[0]);
                // 子进程工作完后关闭读端
                close(pipefd[0]);
                // 子进程工作完后正常退出
                exit(0);
            }

            else
            {
                // father
                // 3. 关闭各自无关端 , 父进程关闭读端
                close(pipefd[0]);
                // 父进程把创建好的管道和子进程插入到信道的管理列表中 , 这样就把信道管理起来了 !
                _chm.Insert(pipefd[1], subid);
            }

            //// ******  完成一个信道的创建 ******
        }

        return true; // 创建成功
    }

public:
    ProcessPool()
    {
        // 注册任务
        _tasks.Regist(UpLoad);
        _tasks.Regist(DownLoad);
        _tasks.Regist(UserRegistration);
    }
    ~ProcessPool() {}

    // 进程池启动 , 一旦启动会做一下事情 :
    // 1 . 先创建进程池
    // 2 . 运行进程池
    void Start()
    {
        //  先创建进程池 , 创还能成功了 , 运行进程池
        if (Create())
        {
            std::cout << "创建进程池成功 !" << std::endl;
            std::cout << "以下为进程池结构 : " << std::endl;
            Debug();
        }

        else
        {
            std::cout << "创建进程池失败 , 请详细检查 !" << std::endl;
            return;
        }
    }

    // 进程池运行  , 运行就是让子进程完成不同任务 , 子进程的任务哪里来 ? 父进程下发任务码告诉子进程完成哪个任务
    void Run()
    {
        std::cout << "进程池运行中 ....." << std::endl;
        std::cout << "----------------" << std::endl;
        // 1. 随机选择一个任务码 , 准备下发
        int taskcode = _tasks.Code();

        // 2. 选择一个信道 , 即 : 发给谁 , 采用轮询的方式 , 子进程均衡的执行任务
        //  所以信道在信道管理列表中 , 所以在这里面选择
        auto &c = _chm.Select(); // 返回的是选择好的一个信道
        std::cout << "Select - > " << c.GetName() << std::endl;
        std::cout << "----------------" << std::endl;
        // 3. 发送任务码 , 具体的子进程工作在 Work 函数中完成
        c.Send(taskcode);
    }

    // 进程池停止
    // 停止就是 : 关闭所有 wfd 端 , 回收所有子进程
    // 但这里存在一个问题 , 请详细看笔者解释 !
    void Stop()
    {
        _chm.CloseAndWait();

        std::cout << "进程池关闭成功 !" << std::endl;
    }

    // 进程池的调试
    void Debug()
    {
        _chm.PrintChannels();
        std::cout << "##########" << std::endl; // 因为加了友元类 , 所以我可以访问 Channel 的私有
    }

private:
    ChannelManage _chm; // 这个里面就是很多信道
    TaskManage _tasks;  // 任务管理列表 , 里面有很多任务
};
/// **********  以上代表一个进程池 , 进程中有很多信道 , 所以包含管理列表就行 **********